package com.fs.client.oauth2.token;

import com.fs.core.security.config.DBConfig;
import com.fs.core.security.util.SecurityUtil;
import com.fs.core.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** 
 * @author pangps
 * @version 创建时间：2017年11月12日 上午22:57:51
 * 类说明 
 * redis集群存储token
*/
 
public class RedisTemplateTokenStore implements TokenStore {
	
	private static final String ACCESS = "access:";
	private static final String AUTH_TO_ACCESS = "auth_to_access:";
	private static final String AUTH = "auth:";
	private static final String REFRESH_AUTH = "refresh_auth:";
	private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
	private static final String REFRESH = "refresh:";
	private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
	private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
	private static final String UNAME_TO_ACCESS = "uname_to_access:";
	/** 默认令牌延续时间 */
	private static final int DEFAULT_TOKEN_CONTINUE_SECONDS = 180;

	@Autowired
	private DBConfig dBConfig;

	/** 令牌存储媒介 */
	@Autowired
	private TokenStore tokenStore;

	@Autowired
	private StringRedisTemplate stringRedisTemplate;


	private RedisTemplate<String,Object> redisTemplate ;
	
	public RedisTemplate<String,Object> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String,Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}


	private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();




	public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
		this.authenticationKeyGenerator = authenticationKeyGenerator;
	}


	public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
		String key = authenticationKeyGenerator.extractKey(authentication);
		OAuth2AccessToken accessToken = (OAuth2AccessToken) redisTemplate.opsForValue().get(AUTH_TO_ACCESS+key);
		if (accessToken != null) {
			String toTokenValue = SecurityUtil.sm4ModelDecode(accessToken.getValue(), dBConfig);
			if(!key.equals(authenticationKeyGenerator.extractKey(readAuthentication(toTokenValue)))) {
				// Keep the stores consistent (maybe the same user is represented by this authentication but the details
				// have changed)
				storeAccessToken(accessToken, authentication);
			}
		}
		return accessToken;
	}


	/**
	 * @Title: replaceBlank
	 * @Description: 去除无效行符
	 * @param
	 * @return String
	 * @throws
	 */
	public String replaceBlank(String str) {
		String dest = "";
		if (str!=null) {
			Pattern p = Pattern.compile("\\s*|\t|\r|\n");
			Matcher m = p.matcher(str);
			dest = m.replaceAll("");
		}
		return dest;
	}

	public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
		String toTokenValue = SecurityUtil.sm4ModelEncode(token.getValue(), dBConfig);
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		// 先从redis获取
		String accessTokenValiditySeconds = (String)stringRedisTemplate.opsForValue().get("accessTokenValiditySeconds");
		if(StringUtil.isEmpty(accessTokenValiditySeconds)){
			stringRedisTemplate.opsForValue().set("accessTokenValiditySeconds", "180000");
			accessTokenValiditySeconds = "180000";
		}
		//配置值
		int pzz =Integer.parseInt(accessTokenValiditySeconds);
		// 判断会话是否超时
		Object tokenLastTime = redisTemplate.opsForValue().get(toTokenValue);
		if (tokenLastTime == null) {
			redisTemplate.opsForValue().set(toTokenValue, new Date());
		} else {
			long lasttime = ((Date) tokenLastTime).getTime();
			Long currenttime = new Date().getTime();

			if (0 != pzz) {
				Long hhscsj = pzz * 1000L;
				if (lasttime + hhscsj >= currenttime || request.getRequestURL().toString().endsWith("/sysLogin/checkPwd")) {//排除登录操作
					redisTemplate.opsForValue().set(toTokenValue, new Date());
				}else{
					tokenStore.removeAccessToken(token);
					// 移除refresh_token
					if (token.getRefreshToken() != null) {
						tokenStore.removeRefreshToken(token.getRefreshToken());
					}
					redisTemplate.delete(toTokenValue);
					throw new InvalidTokenException("会话超时,请重新登录 ");
				}
			}
		}

		return readAuthentication(toTokenValue);
	}

	/**
	 *
	 * @Title: getTokenContinueTime
	 * @Description: 获取令牌延续时间
	 * @return
	 */
	private int getTokenContinueSeconds() {
		/*// 如果存在系统额配置参数则进行延续时间赋值
		String configContinueSeconds = Platform.getPlatform().getString(TOKEN_CONTINUE_SECONDS);
		if (!StringUtils.isEmpty(configContinueSeconds)) {
			return Integer.parseInt(configContinueSeconds);
		}*/
		return DEFAULT_TOKEN_CONTINUE_SECONDS;
	}
	/**
	 *
	 * @Title: addSecond
	 * @Description: 添加时间
	 * @param date
	 * @return
	 */
	private Date addSecond(Date date, int seconds) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.SECOND, seconds);
		return calendar.getTime();
	}

	public OAuth2Authentication readAuthentication(String token) {
		return (OAuth2Authentication) this.redisTemplate.opsForValue().get(AUTH +  token);
	}

	public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
		String toTokenValue = SecurityUtil.sm4ModelEncode(token.getValue(), dBConfig);
		return readAuthenticationForRefreshToken(toTokenValue);
	}
	public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
		return (OAuth2Authentication) this.redisTemplate.opsForValue().get( REFRESH_AUTH+token);
	}
	public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
		
		OAuth2AccessToken existingAccessToken = this.getAccessToken(authentication);

		String toTokenValue = SecurityUtil.sm4ModelEncode(token.getValue(), dBConfig);
		String toTokenValueFresh = SecurityUtil.sm4ModelEncode(token.getRefreshToken().getValue(), dBConfig);

		this.redisTemplate.opsForValue().set(ACCESS+ toTokenValue, token);
		this.redisTemplate.opsForValue().set(AUTH + toTokenValue, authentication);
		this.redisTemplate.opsForValue().set(AUTH_TO_ACCESS+authenticationKeyGenerator.extractKey(authentication), token);
		
		if (!authentication.isClientOnly()) {
			if (existingAccessToken != null) {
				if (!existingAccessToken.isExpired()) {
					int seconds = token.getExpiresIn();
					redisTemplate.expire(UNAME_TO_ACCESS+authentication.getOAuth2Request().getClientId(), seconds, TimeUnit.SECONDS) ;
				}else{
					redisTemplate.opsForList().rightPush(UNAME_TO_ACCESS+getApprovalKey(authentication), token) ;
				}
			}else{
				redisTemplate.opsForList().rightPush(UNAME_TO_ACCESS+getApprovalKey(authentication), token) ;
			}

		}

		if (existingAccessToken != null) {
			if (!existingAccessToken.isExpired()) {
				int seconds = token.getExpiresIn();
				redisTemplate.expire(CLIENT_ID_TO_ACCESS+authentication.getOAuth2Request().getClientId(), seconds, TimeUnit.SECONDS) ;
				
			}else{
				redisTemplate.opsForList().rightPush(CLIENT_ID_TO_ACCESS+authentication.getOAuth2Request().getClientId(), token) ;
			}
		}else{
			redisTemplate.opsForList().rightPush(CLIENT_ID_TO_ACCESS+authentication.getOAuth2Request().getClientId(), token) ;
		}



		if (token.getExpiration() != null) {
			  
			int seconds = token.getExpiresIn();
			redisTemplate.expire(ACCESS+ toTokenValue, seconds, TimeUnit.SECONDS) ;
			redisTemplate.expire(AUTH+ toTokenValue, seconds, TimeUnit.SECONDS) ;
			
			redisTemplate.expire(AUTH_TO_ACCESS+ authenticationKeyGenerator.extractKey(authentication), seconds, TimeUnit.SECONDS) ;
			redisTemplate.expire(CLIENT_ID_TO_ACCESS+authentication.getOAuth2Request().getClientId(), seconds, TimeUnit.SECONDS) ;
			redisTemplate.expire(UNAME_TO_ACCESS+ getApprovalKey(authentication), seconds, TimeUnit.SECONDS) ;
		}
		
		OAuth2RefreshToken refreshToken = token.getRefreshToken();
		
		if (token.getRefreshToken() != null && toTokenValueFresh != null) {
			this.redisTemplate.opsForValue().set( REFRESH_TO_ACCESS+   toTokenValueFresh, token.getValue());
			this.redisTemplate.opsForValue().set(ACCESS_TO_REFRESH + toTokenValue, toTokenValueFresh);
			
			
			if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
				ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
				Date expiration = expiringRefreshToken.getExpiration();
				if (expiration != null) {
					int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
							.intValue();
					
					redisTemplate.expire( REFRESH_TO_ACCESS+   toTokenValueFresh , seconds, TimeUnit.SECONDS) ;
					redisTemplate.expire( ACCESS_TO_REFRESH+ toTokenValue, seconds, TimeUnit.SECONDS) ;
	 
				}
			}


		}
	}

	private String getApprovalKey(OAuth2Authentication authentication) {
		String userName = authentication.getUserAuthentication() == null ? "" : authentication.getUserAuthentication()
				.getName();
		return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
	}

	private String getApprovalKey(String clientId, String userName) {
		return clientId + (userName==null ? "" : ":" + userName);
	}



	public void removeAccessToken(OAuth2AccessToken accessToken) {
		removeAccessToken(accessToken.getValue());
	}

	public OAuth2AccessToken readAccessToken(String tokenValue) {
		return (OAuth2AccessToken) this.redisTemplate.opsForValue().get(ACCESS+tokenValue);
	}

	public void removeAccessToken(String tokenValue) {
		OAuth2AccessToken removed = (OAuth2AccessToken) redisTemplate.opsForValue().get(ACCESS+tokenValue);
		// Don't remove the refresh token - it's up to the caller to do that
		OAuth2Authentication authentication = (OAuth2Authentication) this.redisTemplate.opsForValue().get(AUTH+tokenValue);


		this.redisTemplate.delete(AUTH+tokenValue);
		redisTemplate.delete(ACCESS+tokenValue);
		this.redisTemplate.delete(ACCESS_TO_REFRESH +tokenValue);


		if (authentication != null) {
			this.redisTemplate.delete(AUTH_TO_ACCESS+authenticationKeyGenerator.extractKey(authentication));
		
			String clientId = authentication.getOAuth2Request().getClientId();
			
//			redisTemplate.opsForList().rightPush("UNAME_TO_ACCESS:"+getApprovalKey(authentication), token) ;
			redisTemplate.opsForList().leftPop(UNAME_TO_ACCESS+getApprovalKey(clientId, authentication.getName()));
			
			redisTemplate.opsForList().leftPop(CLIENT_ID_TO_ACCESS+clientId);
			
			this.redisTemplate.delete(AUTH_TO_ACCESS+authenticationKeyGenerator.extractKey(authentication));
		}
	}

	public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
		this.redisTemplate.opsForValue().set(REFRESH+refreshToken.getValue(), refreshToken);
		this.redisTemplate.opsForValue().set( REFRESH_AUTH + refreshToken.getValue(), authentication);
	}

	public OAuth2RefreshToken readRefreshToken(String tokenValue) {
		return (OAuth2RefreshToken) this.redisTemplate.opsForValue().get(REFRESH+tokenValue);
	}

	public void removeRefreshToken(OAuth2RefreshToken refreshToken) {
		removeRefreshToken(refreshToken.getValue());
	}

	public void removeRefreshToken(String tokenValue) {
		this.redisTemplate.delete( REFRESH + tokenValue);
		this.redisTemplate.delete( REFRESH_AUTH + tokenValue);
		this.redisTemplate.delete(REFRESH_TO_ACCESS +tokenValue);
	}

	public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
		removeAccessTokenUsingRefreshToken(refreshToken.getValue());
	}

	private void removeAccessTokenUsingRefreshToken(String refreshToken) {
		
		String token = (String) this.redisTemplate.opsForValue().get( REFRESH_TO_ACCESS  +refreshToken) ;
		
		if (token != null) {
			redisTemplate.delete(REFRESH_TO_ACCESS+ refreshToken);
		}
	}

	public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
		List<Object> result =    redisTemplate.opsForList().range(UNAME_TO_ACCESS+ getApprovalKey(clientId, userName), 0, -1);
		
		if (result == null || result.size() == 0) {
			return Collections.<OAuth2AccessToken> emptySet();
		}
		List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(result.size());
		
		for(Iterator<Object> it = result.iterator();it.hasNext();){
			OAuth2AccessToken accessToken = (OAuth2AccessToken) it.next();
			accessTokens.add(accessToken);
		}
		
		return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens); 
	}

	public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
		List<Object> result =    redisTemplate.opsForList().range((CLIENT_ID_TO_ACCESS+clientId), 0, -1);
		
		if (result == null || result.size() == 0) {
			return Collections.<OAuth2AccessToken> emptySet();
		}
		List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(result.size());
		
		for(Iterator<Object> it = result.iterator();it.hasNext();){
			OAuth2AccessToken accessToken = (OAuth2AccessToken) it.next();
			accessTokens.add(accessToken);
		}
		
		return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens); 
	}




}

